/*
 * Copyright (c) 2021-2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// Autogenerated file. DO NOT EDIT

package escompat

{%- for N, T, S in [
    ('Int8', 'byte', 1),
    ('Int16', 'short', 2),
    ('Int32', 'int', 4),
    ('BigInt64', 'long', 8),
    ('Float32', 'float', 4),
    ('Float64', 'double', 8)]
%}

    {%- set elementCompat = 'number' if T != 'long' else 'BigInt' %}
    {%- set asElementCompat = '%s as number' if elementCompat == 'number' else 'new BigInt(%s)' %}
    {%- set fromElementCompat = (' as ' + T) if elementCompat == 'number' else '.getLong()' %}
    {%- set subsetTypeValues = 'Number' if elementCompat == 'number' else 'BigInt' %}
    {%- set numberBigIntToPrimitive = ('.' + T + 'Value()') if elementCompat == 'number' else '.getLong()' %}
    {%- set numberBigIntUndefToPrimitive = (numberBigIntToPrimitive) %}

class {{N}}ArrayIteratorKeys implements IterableIterator<number> {
    private length: int
    private idx: int = 0

    constructor(parent: {{N}}Array) {
        this.length = parent.length as int
    }

    public override $_iterator(): IterableIterator<number> {
        return this
    }

    override next(): IteratorResult<number> {
        if (this.idx < 0 || this.idx >= this.length) {
            return new IteratorResult<number>()
        }
        return new IteratorResult<number>(false, this.idx++ as number)
    }
}

class {{N}}ArrayIterator implements IterableIterator<{{subsetTypeValues}}> {
    private parent: {{N}}Array
    private idx: int = 0

    constructor(parent: {{N}}Array) {
        this.parent = parent
    }

    public override $_iterator(): IterableIterator<{{subsetTypeValues}}> {
        return this
    }

    override next(): IteratorResult<{{subsetTypeValues}}> {
        if (this.idx < 0 || this.idx >= this.parent.length as int) {
            return new IteratorResult<{{subsetTypeValues}}>()
        }
        return new IteratorResult<{{subsetTypeValues}}>(false, new {{subsetTypeValues}}(this.parent[this.idx++]))
    }
}

{% set subsetTypeValuesEntries = '[' + 'Number' + ', ' + subsetTypeValues + ']' -%}

class {{N}}ArrayIteratorEntries implements IterableIterator<{{subsetTypeValuesEntries}}> {
    private parent: {{N}}Array
    private idx: int = 0

    constructor(parent: {{N}}Array) {
        this.parent = parent
    }

    public override $_iterator(): IterableIterator<{{subsetTypeValuesEntries}}> {
        return this
    }

    override next(): IteratorResult<{{subsetTypeValuesEntries}}> {
        if (this.idx < 0 || this.idx >= this.parent.length as int) {
            return new IteratorResult<{{subsetTypeValuesEntries}}>()
        }
        return new IteratorResult<{{subsetTypeValuesEntries}}>(
            false, [new Number(this.idx), new {{subsetTypeValues}}(this.parent[this.idx++])]{{ '' if elementCompat != 'BigInt' else ' as ' + subsetTypeValuesEntries }}
        )
    }
}


/**
 * JS {{N}}Array API-compatible class
 */
export final class {{N}}Array implements Iterable<{{subsetTypeValues}}>, ArrayLike<{{subsetTypeValues}}> {
    public static readonly BYTES_PER_ELEMENT: number = {{S}}
    public readonly BYTES_PER_ELEMENT: number = {{N}}Array.BYTES_PER_ELEMENT
    internal readonly lengthInt: int

    /**
     * Creates an empty {{N}}Array.
     */
    public constructor() {
        this(0 as int)
    }

    /**
     * Creates an {{N}}Array with respect to data accessed via Iterable<Number> interface
     */
    public constructor(elements: Iterable<{{subsetTypeValues}}>) {
        const items: Object = elements as Object
        if (items instanceof ArrayLike) {
            const arr = Types.identity_cast<{{subsetTypeValues}}>(items as ArrayLike<{{subsetTypeValues}}>)
            this.byteLength = arr.length as int * {{N}}Array.BYTES_PER_ELEMENT as int
            this.lengthInt = arr.length as int
            this.buffer = new ArrayBuffer(this.byteLength as int)
            this.byteOffset = 0
            for (let i: int = 0; i < this.lengthInt; ++i) {
                {%- if N == 'BigInt64' %}
                this.setUnsafe(i, arr.$_get(i).getLong())
                {%- else %}
                this.setUnsafe(i, arr.$_get(i).to{{T.capitalize()}}())
                {%- endif %}
            }
        } else {
          let x = {{N}}Array.from(elements)
          this.byteLength = x.byteLength
          this.lengthInt = x.lengthInt
          this.buffer = x.buffer
          this.byteOffset = x.byteOffset
        }
    }

    /**
     * Creates an {{N}}Array with respect to data, byteOffset and length.
     *
     * @param buf data initializer
     *
     * @param byteOffset byte offset from begin of the buf
     *
     * @param length size of elements of type {{T}} in newly created {{N}}Array
     */
    public constructor(buf: ArrayBuffer, byteOffset: Number | undefined, length: Number | undefined) {
        let intByteOffset: int = 0
        if (byteOffset != undefined) {
            intByteOffset = byteOffset.toInt()
            if (intByteOffset < 0) {
                throw new RangeError("Range Error: byteOffset " + intByteOffset + " is outside the bounds of the buffer")
            }
        }
        let intByteLength: int = buf.getByteLength() - intByteOffset
        if (intByteLength < 0) {
            throw new RangeError("Range Error: byteLength " + intByteLength + " is outside the bounds of the buffer with byteOffset " + intByteOffset)
        }
{% if S != 1 %}
        if (intByteLength % {{N}}Array.BYTES_PER_ELEMENT as int != 0) {
            throw new RangeError("ArrayBuffer.byteLength should be multiple of {{S}} as {{N}}Array.BYTES_PER_ELEMENT")
        }
        if (intByteOffset % {{N}}Array.BYTES_PER_ELEMENT as int != 0) {
            throw new RangeError("byteOffset should be multiple of {{S}} as {{N}}Array.BYTES_PER_ELEMENT")
        }
{% endif %}
        let intLength: int
        if (length != undefined) {
            intLength = length.toInt()
            if (intLength > intByteLength / {{N}}Array.BYTES_PER_ELEMENT as int) {
                throw new RangeError("Range Error: length " + intLength + " is outside the bounds of the buffer with byteOffset " + intByteOffset)
            }
        } else {
            intLength = intByteLength / {{N}}Array.BYTES_PER_ELEMENT as int
        }
        if (intLength < 0) {
            throw new RangeError("Range Error: length " + intLength + " is outside the bounds of the buffer")
        }
        if (intLength < intByteLength / {{N}}Array.BYTES_PER_ELEMENT as int) {
            intByteLength = intLength * {{N}}Array.BYTES_PER_ELEMENT as int
        }
        this.byteLength = intByteLength
        this.byteOffset = intByteOffset
        this.lengthInt = intLength
        this.buffer = buf
    }

    /**
     * Creates an {{N}}Array with respect to data, byteOffset and length.
     *
     * @param buf data initializer
     *
     * @param byteOffset byte offset from begin of the buf
     *
     * @param length size of elements of type {{T}} in newly created {{N}}Array
     */
    public constructor(buf: ArrayBuffer, byteOffset: Number | undefined) {
        this(buf, byteOffset, undefined)
    }

    /**
     * Creates an {{N}}Array with respect to data, byteOffset and length.
     *
     * @param buf data initializer
     *
     * @param byteOffset byte offset from begin of the buf
     *
     * @param length size of elements of type {{T}} in newly created {{N}}Array
     */
    public constructor(buf: ArrayBuffer, byteOffset: number, length: number) {
        this(buf, new Number(byteOffset), new Number(length))
    }

    /**
     * Creates an {{N}}Array with respect to data, byteOffset and length.
     *
     * @param buf data initializer
     *
     * @param byteOffset byte offset from begin of the buf
     *
     * @param length size of elements of type {{T}} in newly created {{N}}Array
     */
    public constructor(buf: ArrayBuffer, byteOffset: number) {
        this(buf, new Number(byteOffset), undefined)
    }

    /**
     * Creates an {{N}}Array with respect to data, byteOffset and length.
     *
     * @param buf data initializer
     *
     * @param byteOffset byte offset from begin of the buf
     *
     * @param length size of elements of type {{T}} in newly created {{N}}Array
     */
    public constructor(buf: ArrayBuffer, byteOffset: int, length: int) {
        this(buf, new Number(byteOffset), new Number(length))
    }

    /**
     * Creates an {{N}}Array with respect to buf and byteOffset.
     *
     * @param buf data initializer
     *
     * @param byteOffset byte offset from begin of the buf
     */
    public constructor(buf: ArrayBuffer, byteOffset: int) {
        this(buf, new Number(byteOffset), undefined)
    }

    /**
     * Creates an {{N}}Array with respect to buf.
     *
     * @param buf data initializer
     */
    public constructor(buf: ArrayLike<Number> | ArrayBuffer) {
        if (buf instanceof ArrayBuffer) {
            this.byteLength = (buf as ArrayBuffer).getByteLength()
            if (this.byteLength % {{N}}Array.BYTES_PER_ELEMENT as int != 0) {
               throw new RangeError("ArrayBuffer.byteLength should be multiple of {{S}} as {{N}}Array.BYTES_PER_ELEMENT")
            }
            this.lengthInt = this.byteLength / {{N}}Array.BYTES_PER_ELEMENT as int
            this.buffer = buf as ArrayBuffer
            this.byteOffset = 0
        } else if (buf instanceof ArrayLike) {
            // NOTE (ikorobkov): dealing with this overload is tricky
            // with banned `instanceof` generic, so it is delegated to array here. Initial idea from Set.ets
            let arr = Array.from<Number>((buf as ArrayLike<Number>))
            this.byteLength = arr.length as int * {{N}}Array.BYTES_PER_ELEMENT as int
            this.lengthInt = arr.length as int
            this.buffer = new ArrayBuffer(this.byteLength as int)
            this.byteOffset = 0
            for (let i: int = 0; i < this.lengthInt; ++i) {
                this.setUnsafe(i, arr.$_get(i).to{{T.capitalize()}}())
            }
        } else {
            throw new Error("unexpected type of buf")
        }
    }

    /**
     * Creates an {{N}}Array with respect to length.
     *
     * @param length data initializer
     */
    public constructor(length: int) {
        this(length as number)
    }

    /**
     * Creates an {{N}}Array with respect to length.
     *
     * @param length data initializer
     */
    public constructor(length: number) {
        if (length < 0 || length > (Int.MAX_VALUE / {{N}}Array.BYTES_PER_ELEMENT)) {
            throw new TypeError("Type Error: length " + length + " is outside the bounds of the buffer")
        }
        this.lengthInt = length as int
        this.byteLength = this.lengthInt * {{N}}Array.BYTES_PER_ELEMENT as int
        this.byteOffset = 0
        this.buffer = new ArrayBuffer(this.byteLength as int)
    }

    /**
     * Creates a copy of {{N}}Array.
     *
     * @param other data initializer
     */
    public constructor(other: {{N}}Array) {
        this.buffer = other.buffer.slice(other.byteOffset as int, (other.byteOffset + other.byteLength) as int) as ArrayBuffer
        this.byteLength = other.byteLength
        this.lengthInt = other.length as int
        this.byteOffset = 0
    }

    /**
     * Creates an {{N}}Array from FixedArray<number>
     */
    public constructor(numbers: FixedArray<number>) {
        this(numbers.length)
        for (let i: int = 0; i < this.lengthInt; ++i) {
            {%- if N == 'BigInt64' %}
            this.setUnsafe(i, numbers[i] as long)
            {%- elif N not in ['Float32', 'Float64'] %}
            this.setUnsafe(i, {{ ('this.zeroIfInfinity(numbers[i])' + ' as ' + T)
                if elementCompat != 'BigInt' else 'this.zeroIfInfinity(numbers[i] as long)' }})
            {%- else %}
            this.setUnsafe(i, numbers[i]{{fromElementCompat}})
            {%- endif %}
        }
    }

    /**
     * Creates an {{N}}Array from FixedArray<int>
     */
    public constructor(numbers: FixedArray<int>) {
        this(numbers.length)
        for (let i: int = 0; i < this.lengthInt; ++i) {
            {%- if N == 'BigInt64' %}
            this.setUnsafe(i, numbers[i] as long)
            {%- elif N not in ['Float32', 'Float64'] %}
            this.setUnsafe(i, {{ ('this.zeroIfInfinity(numbers[i])' + ' as ' + T)
                            if elementCompat != 'BigInt' else 'this.zeroIfInfinity(numbers[i] as long)' }})
            {%- else %}
            this.setUnsafe(i, numbers[i]{{fromElementCompat}})
            {%- endif %}
        }
    }

    {%- if N == 'BigInt64' %}

    /**
     * Creates an {{N}}Array from FixedArray<bigint>
     */
    public constructor(numbers: FixedArray<bigint>) {
        this(numbers.length)
        for (let i: int = 0; i < this.lengthInt; ++i) {
            this.setUnsafe(i, numbers[i]{{fromElementCompat}})
        }
    }
    {%- endif %}

    {%- if N not in ['Float32', 'Float64', 'BigInt64'] %}

    internal zeroIfInfinity(val: {{elementCompat}}): {{elementCompat}} {
        {%- if elementCompat == 'number' %}
        if ((val == Infinity) || (val == -Infinity)) {
        {%- else %}
        if ((val{{fromElementCompat}} == Infinity) || (val{{fromElementCompat}} == -Infinity)) {
        {%- endif %}
            return {{asElementCompat % '0'}}
        }
        return {{asElementCompat % 'val'}}
    }

    internal zeroIfInfinity(val: {{T}}): {{T}} {
        if ((val == Infinity) || (val == -Infinity)) {
            return 0 as {{T}}
        }
        return val
    }
    {%- endif %}

    {%- if T != 'double' %}

    /**
     * Assigns val as element on index.
     *
     * @param val value to set
     *
     * @param index index to change
     */
    public $_set(index: number, val: {{elementCompat}}): void {
        this.$_set(index as int, val)
    }

    /**
     * Assigns val as element on index.
     *
     * @param val value to set
     *
     * @param index index to change
     */
    public $_set(index: int, val: {{elementCompat}}): void {
        {%- if not N in ['Float32', 'Float64', 'BigInt64'] %}
        let v = this.zeroIfInfinity(val)
        this.$_set(index, v{{fromElementCompat}})
        {%- else %}
        this.$_set(index, val{{fromElementCompat}})
        {%- endif %}
    }
    {%- endif %}
    {%- if N == 'Int8' or N == 'Int16' %}

    /**
     * Assigns val as element on index.
     *
     * @param val value to set
     *
     * @param index index to change
     */
    public $_set(index: number, val: int): void {
        this.$_set(index as int, val)
    }

    /**
     * Assigns val as element on index.
     *
     * @param val value to set
     *
     * @param index index to change
     */
    public native $_set(index: int, val: int): void
    {%- endif %}

    /**
     * Assigns val as element on index.
     *
     * @param val value to set
     *
     * @param index index to change
     */
    public $_set(index: number, val: {{T}}): void {
        this.$_set(index as int, val)
    }

    /**
     * Assigns val as element on index.
     *
     * @param val value to set
     *
     * @param index index to change
     */
    public native $_set(index: int, val: {{T}}): void

    /** Number of {{T}} stored in {{N}}Array */
    public get length(): number {
        return this.lengthInt
    }

    /**
     * Returns an instance of {{elementCompat}} at passed index.
     *
     * @param index index to look at
     *
     * @returns a primitive at index
     */
    public override $_get(index: number): {{subsetTypeValues}} {
        return this.$_get(index as int) as {{subsetTypeValues}}
    }

    /**
     * Returns an instance of {{elementCompat}} at passed index.
     *
     * @param index index to look at
     *
     * @returns a primitive at index
     */
    {%- if T != 'long' %}
    public native $_get(index: int): {{elementCompat}}
    {%- else %}
    public $_get(index: int): {{elementCompat}} {
        return {{asElementCompat % 'this.getLong(index)'}}
    }

    private native getLong(index: int): long
    {%- endif %}

    {%- if T != 'long' %}

    /**
     * Returns an instance of primitive type at passed index.
     *
     * @param index index to look at
     *
     * @returns a primitive at index
     */
    public at(index: number): Number | undefined {
        return this.at(index as int)
    }

    /**
     * Returns an instance of primitive type at passed index.
     *
     * @param index index to look at
     *
     * @returns a primitive at index
     */
    public at(index: int): Number | undefined {
        let k: int
        if (index >= 0) {
            k = index
        } else {
            k = this.lengthInt + index
        }
        if (k < 0 || k >= this.lengthInt) {
            return undefined
        }
        return new Number(this.getUnsafe(k))
    }
    {%- else %}

    /**
     * Returns an instance of primitive type at passed index.
     *
     * @param index index to look at
     *
     * @returns a primitive at index
     */
    public at(index: number): {{elementCompat}} | undefined {
        return this.at(index as int)
    }

    /**
     * Returns an instance of primitive type at passed index.
     *
     * @param index index to look at
     *
     * @returns a primitive at index
     */
    public at(index: int): {{elementCompat}} | undefined {
        let k: int
        if (index >= 0) {
            k = index
        } else {
            k = this.lengthInt + index
        }
        if (k < 0 || k >= this.lengthInt) {
            return undefined
        }
        return {{asElementCompat % 'this.getUnsafe(k)'}}
    }
    {%- endif %}

    /**
     * Makes a copy of internal elements to targetPos from startPos to endPos.
     *
     * @param target insert index to place copied elements
     *
     * @param start start index to begin copy from
     *
     * @param end last index to end copy from, excluded
     *
     * See rules of parameters normalization on {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin | MDN}
     */
    public copyWithin(target: number, start: number, end?: number): {{N}}Array {
        return this.copyWithin(target as int, start as int, asIntOrDefault(end, this.lengthInt))
    }

    /**
     * Makes a copy of internal elements to targetPos from startPos to endPos.
     *
     * @param target insert index to place copied elements
     *
     * @param start start index to begin copy from
     *
     * @param end last index to end copy from, excluded
     *
     * See rules of parameters normalization on {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin | MDN}
     */
    public copyWithin(target: int, start: number, end?: number): {{N}}Array {
        return this.copyWithin(target as int, start as int, asIntOrDefault(end, this.lengthInt))
    }

    /**
     * Makes a copy of internal elements to targetPos from startPos to endPos.
     *
     * @param target insert index to place copied elements
     *
     * @param start start index to begin copy from
     *
     * @param end last index to end copy from, excluded
     *
     * See rules of parameters normalization on {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin | MDN}
     */
    public copyWithin(target: number, start: int, end?: number): {{N}}Array {
        return this.copyWithin(target as int, start as int, asIntOrDefault(end, this.lengthInt))
    }

    /**
     * Makes a copy of internal elements to targetPos from startPos to endPos.
     *
     * @param target insert index to place copied elements
     *
     * @param start start index to begin copy from
     *
     * @param end last index to end copy from, excluded
     *
     * See rules of parameters normalization on {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin | MDN}
     */
    public copyWithin(target: int, start: int, end?: number): {{N}}Array {
        return this.copyWithin(target as int, start as int, asIntOrDefault(end, this.lengthInt))
    }

    /**
     * Makes a copy of internal elements to targetPos from startPos to endPos.
     *
     * @param target insert index to place copied elements
     *
     * @param start start index to begin copy from
     *
     * @param end last index to end copy from, excluded
     *
     * See rules of parameters normalization on {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin | MDN}
     */
    public native copyWithin(target: int, start: int, end: int): {{N}}Array

    /**
     * Makes a copy of internal elements to targetPos from begin to end of {{N}}Array.
     *
     * @param target insert index to place copied elements
     *
     * See rules of parameters normalization:
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin}
     */
    public copyWithin(target: number): {{N}}Array {
        return this.copyWithin(target as int)
    }

    /**
     * Makes a copy of internal elements to targetPos from begin to end of {{N}}Array.
     *
     * @param target insert index to place copied elements
     *
     * See rules of parameters normalization:
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/copyWithin}
     */
    public copyWithin(target: int): {{N}}Array {
        return this.copyWithin(target, 0, this.lengthInt)
    }

    {% set TBoxed = T[0].upper() + T[1:] -%}

    /**
     * Returns an array of key, value pairs for every entry in the {{N}}Array
     *
     * @returns key, value pairs for every entry in the array
     */
    public entries(): IterableIterator<{{subsetTypeValuesEntries}}> {
        return new {{N}}ArrayIteratorEntries(this)
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{elementCompat}}, start?: number, end?: number): this {
        {%- if not N in ['Float32', 'Float64', 'BigInt64'] %}
        value = this.zeroIfInfinity(value)
        {%- endif %}
        this.fill(value{{fromElementCompat}}, asIntOrDefault(start, 0 as int), asIntOrDefault(end, this.lengthInt))
        return this
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{elementCompat}}, start: int, end?: number): this {
        {%- if not N in ['Float32', 'Float64', 'BigInt64'] %}
        value = this.zeroIfInfinity(value)
        {%- endif %}
        this.fill(value{{fromElementCompat}}, start as int, asIntOrDefault(end, this.lengthInt))
        return this
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{elementCompat}}, start: int, end: number): this {
        {%- if not N in ['Float32', 'Float64', 'BigInt64'] %}
        value = this.zeroIfInfinity(value)
        {%- endif %}
        this.fill(value{{fromElementCompat}}, start as int, end as int)
        return this
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{elementCompat}}, start: number, end: int): this {
        {%- if not N in ['Float32', 'Float64', 'BigInt64'] %}
        value = this.zeroIfInfinity(value)
        {%- endif %}
        this.fill(value{{fromElementCompat}}, start as int, end as int)
        return this
    }

    {%- if N != 'Float64' %}

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{elementCompat}}, start: int, end: int): this {
        {%- if N not in ['Float32', 'BigInt64'] %}
        value = this.zeroIfInfinity(value)
        {%- endif %}
        this.fill(value{{fromElementCompat}}, start as int, end as int)
        return this
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{T}}, start?: number, end?: number): this {
        this.fill(value, asIntOrDefault(start, 0 as int), asIntOrDefault(end, this.lengthInt))
        return this
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{T}}, start: int, end?: number): this {
        this.fill(value, start as int, asIntOrDefault(end, this.lengthInt))
        return this
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{T}}, start: int, end: number): this {
        this.fill(value, start as int, end as int)
        return this
    }

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{T}}, start: number, end: int): this {
        this.fill(value, start as int, end as int)
        return this
    }

    {%- endif %}

    /**
     * Fills the {{N}}Array with specified value
     *
     * @param value new valuy
     *
     * @returns modified {{N}}Array
     */
    public fill(value: {{T}}, start: int, end: int): this {
        const k = normalizeIndex(start, this.lengthInt)
        const finalPos = normalizeIndex(end, this.lengthInt)
        this.fillInternal(value, k, finalPos)
        return this
    }

    private final native fillInternal(value: {{T}}, start: int, end: int): void

    /**
     * Assigns val as element on insertPos.
     * @description Added to avoid (un)packing a single value into array to use overloaded set(FixedArray<{{T}}>, insertPos)
     *
     * @param val value to set
     *
     * @param insertPos index to change
     */
    public set(insertPos: number, val: {{elementCompat}}): void {
        this.$_set(insertPos, val)
    }

    /**
     * Assigns val as element on insertPos.
     * @description Added to avoid (un)packing a single value into array to use overloaded set(FixedArray<{{T}}>, insertPos)
     *
     * @param val value to set
     *
     * @param insertPos index to change
     */
    public set(insertPos: int, val: {{T}}): void {
        this.$_set(insertPos, val)
    }

    /**
     * Copies all elements of arr to the current {{N}}Array starting from insertPos.
     *
     * @param arr array to copy data from
     *
     * @param insertPos start index where data from arr will be inserted
     *
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set}
     */
    public set(arr: FixedArray<{{elementCompat}}>, insertPos: number): void {
        const offset = insertPos as int
        if (offset < 0 || offset + arr.length > this.lengthInt) {
            throw new RangeError("offset is out of bounds")
        }
        for (let i = 0; i < arr.length as int; ++i) {
        {%- if (N == 'BigInt64') %}
            let truncatedVal: BigInt = BigInt.asIntN(64, arr[i])
            this.$_set(offset + i, truncatedVal{{fromElementCompat}})
        {%- elif (N != 'Float32') and (N != 'Float64') %}
            let v = this.zeroIfInfinity(arr[i])
            this.$_set(offset + i, v{{fromElementCompat}})
        {%- else %}
            this.$_set(offset + i, arr[i]{{fromElementCompat}})
        {%- endif %}
        }
    }

    /**
     * Copies all elements of arr to the current {{N}}Array starting from insertPos.
     *
     * @param arr array to copy data from
     *
     * @param insertPos start index where data from arr will be inserted
     *
     * {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set}
     */
    public set(arr: FixedArray<{{T}}>, insertPos: int): void {
        const offset = insertPos as int
        if (offset < 0 || offset + arr.length > this.lengthInt) {
            throw new RangeError("offset is out of bounds")
        }
        for (let i = 0; i < arr.length as int; ++i) {
        {%- if not N in ['Float32', 'Float64', 'BigInt64'] %}
            let v = this.zeroIfInfinity(arr[i])
            this.$_set(offset + i, v)
        {%- else %}
            this.$_set(offset + i, arr[i])
        {%- endif %}
        }
    }
    {%- if T != 'double' %}

    /**
     * Copies all elements of arr to the current {{N}}Array.
     *
     * @param arr array to copy data from
     */
    public set(arr: FixedArray<{{elementCompat}}>): void {
        this.set(arr, 0 as number)
    }
    {%- endif %}

    /**
     * Copies all elements of arr to the current {{N}}Array.
     *
     * @param arr array to copy data from
     */
    public set(arr: FixedArray<{{T}}>): void {
        this.set(arr, 0 as int)
    }

    public native set(array: {{N}}Array): void

    public native set(array: {{N}}Array, offset: number): void

    /**
     * Copies elements from an ArrayLike object to the {{N}}Array.
     *
     * @param array An ArrayLike object containing the elements to copy.
     *
     * @param offset Optional. The offset into the target array at which to begin writing values from the source array
     */
    public set(array: ArrayLike<{% if N == 'BigInt64'%}BigInt{% else %}number{% endif %}>, offset: number = 0): void {
        const insertPos = offset as int
        if (insertPos < 0 || insertPos + array.length > this.lengthInt) {
            throw new RangeError("offset is out of bounds")
        }
        for (let i = array.length - 1 as int; i >= 0; --i) {
        {%- if (N == 'BigInt64') %}
            let truncatedVal: BigInt = BigInt.asIntN(64, array[i])
            this.$_set(offset + i, truncatedVal{{fromElementCompat}})
        {%- elif (N != 'Float32') and (N != 'Float64') %}
            let v = this.zeroIfInfinity(array[i])
            this.$_set(insertPos + i, v{{fromElementCompat}})
        {%- else %}
            this.$_set(insertPos + i, array[i]{{fromElementCompat}})
        {%- endif %}
        }
    }
    {%- set uniquePrimitiveTypes = ['number', 'int', T] if T != 'int' else ['number', 'int'] %}
    {%- set uniquePrimitiveTypes = uniquePrimitiveTypes if N != 'BigInt64' else uniquePrimitiveTypes + ['bigint'] %}
    {%- for argType in uniquePrimitiveTypes %}
        {%- if argType != 'double' %}

    /**
     * Returns a new array from a set of elements.
     *
     * @param items a set of elements to include in the new array object.
     *
     * @returns new {{N}}Array
     */
    public static of(...items: FixedArray<{{argType}}>): {{N}}Array {
        let res = new {{N}}Array(items.length as int)
        for (let i: int = 0; i < items.length; i++) {
        {%- if argType == 'number' %}
            {%- if N == 'BigInt64' %}
            res.setUnsafe(i, items[i] as long)
            {%- elif (N != 'Float32') and (N != 'Float64') %}
            res.setUnsafe(i, res.zeroIfInfinity(items[i]){{fromElementCompat}})
            {%- else %}
            res.setUnsafe(i, items[i]{{ ' as ' + T if argType != T and T != 'double' else ''}})
            {%- endif %}
        {%- else %}
            {%- if N == 'BigInt64' and argType == 'bigint' %}
            res.setUnsafe(i, items[i].getLong())
            {%- else %}
            res.setUnsafe(i, items[i]{{ ' as ' + T if argType != T else ''}})
            {%- endif %}
        {%- endif %}
        }
        return res
    }
        {%- endif %}
    {%- endfor %}

    /**
     * Returns a new array from a set of elements.
     *
     * @param items a set of elements to include in the new array object.
     *
     * @returns new {{N}}Array
     */
    public static of(): {{N}}Array {
        return new {{N}}Array(0 as int)
    }

    /**
     * Creates an array from an array-like or iterable object.
     *
     * @param arrayLike An array-like or iterable object to convert to an array.
     *
     * @returns new {{N}}Array
     */
    public static from(arrayLike: ArrayLike<number>): {{N}}Array {
    {%- if N != 'BigInt64' %}
        return {{N}}Array.from<number>(arrayLike, (x: number, k: number): number => x)
    {%- else %}
        throw new Error("{{N}}Array.from: not implemented")
    {%- endif %}
    }

    /**
     * Creates an array from an array-like or iterable object.
     *
     * @param arrayLike An array-like or iterable object to convert to an array.
     *
     * @param mapfn A mapping function to call on every element of the array.
     *
     * @returns new {{N}}Array
     */
    public static from(arrayLike: Iterable<{{elementCompat}}>, mapfn?: (v: {{elementCompat}}, k: number) => {{elementCompat}}): {{N}}Array {
        if (mapfn == undefined) {
            mapfn = (v: {{elementCompat}}, k: number): {{elementCompat}} => { return v }
        }

        let iter = arrayLike.$_iterator()
        // NOTE (templin.konstantin): This code section is responsible for optimizing for some types
        //  of iterators that we can learn the length of (e.g. ArrayValuesIterator).
        //  We are trying to use "reflection" to find the "__Iterator_getLength" method and if it exists,
        //  we can make one pass through the iterator without the need for memory reallocation.
        const maybeLength = tryGetIteratorLength(arrayLike)
        if (maybeLength) {
            const result = new {{N}}Array(maybeLength)
            for (let i = 0; i < maybeLength; ++i) {
                const x = iter.next()
                if (x.done) {
                    return new {{N}}Array(result.buffer, 0, i)
                }
                {%- if N not in ['Float32', 'Float64', 'BigInt64'] %}
                result.setUnsafe(i, result.zeroIfInfinity((mapfn)!(x.value!, i)){{fromElementCompat}})
                {%- else %}
                result.setUnsafe(i, (mapfn)!(x.value!, i){{fromElementCompat}})
                {%- endif %}
            }
            return result
        }

        // NOTE (templin.konstantin): Create builtin array as buffer
        let temp = new {{N}}Array(6)
        let index : FixedArray<int> = new int[1]
        index[0] = 0

        iteratorForEach<{{elementCompat}}>(iter, (x: {{elementCompat}}): void => {
            if (index[0] + 1 > temp.lengthInt) {
                // NOTE (templin.konstantin): Progressive reallocation
                const curLength = (temp.buffer as Buffer).getByteLength()
                const tb = new ArrayBuffer(curLength * 2)
                for (let i = 0; i < curLength; ++i) {
                    tb.set(i, (temp.buffer as Buffer).at(i))
                }
                temp = new {{N}}Array(tb)
            }
            {%- if N not in ['Float32', 'Float64', 'BigInt64'] %}
            temp.setUnsafe(index[0], temp.zeroIfInfinity((mapfn)!(x, index[0])){{fromElementCompat}})
            {%- else %}
            temp.setUnsafe(index[0], (mapfn)!(x, index[0]){{fromElementCompat}})
            {%- endif %}
            index[0]++
        })
        return new {{N}}Array(temp.buffer, 0, index[0])
    }


    /**
     * Creates an array from an array-like or iterable object.
     *
     * @param arrayLike An array-like or iterable object to convert to an array.
     *
     * @param mapfn A mapping function to call on every element of the array.
     *
     * @returns new {{N}}Array
     */
    public static from<T>(arrayLike: ArrayLike<T>, mapfn: (v: T, k: number) => {{elementCompat}}): {{N}}Array {
        let res = new {{N}}Array(arrayLike.length)
        // NOTE (ikorobkov): Please don't replace idx as int[1] with int-variable, because of value of single variable doesn't change (idx++) into lambda call by unknown reason
        const idx : FixedArray<int> = new int[1]
        idx[0] = 0
        iteratorForEach<T>(arrayLike.$_iterator(), (x: T): void => {
            {%- if N not in ['Float32', 'Float64', 'BigInt64'] %}
            res.setUnsafe(idx[0] as int, res.zeroIfInfinity(mapfn(x as T, idx[0] as number)){{fromElementCompat}})
            {%- else %}
            res.setUnsafe(idx[0] as int, mapfn(x as T, idx[0] as number){{fromElementCompat}})
            {%- endif %}
            idx[0] += 1
        })
        return res
    }

    /**
     * Determines whether {{N}}Array includes a certain element, returning true or false as appropriate
     *
     * @param searchElement The element to search for
     *
     * @param fromIndex The position in this array at which to begin searching for searchElement
     *
     * @returns true if searchElement is in {{N}}Array, false otherwise
     */
    public includes(searchElement: {{elementCompat}}, fromIndex?: number): boolean {
    {%- if (N != 'Float32') and (N != 'Float64') %}
        {%- if elementCompat == 'number' %}
        if (isNaN(searchElement)) {
            return false
        }
        {%- endif %}
    {%- else %}
        if (isNaN(searchElement)) {
            let fromIndexInt: int = normalizeIndex(asIntOrDefault(fromIndex, 0), this.lengthInt)
            for (let i = fromIndexInt; i < this.lengthInt; i++) {
                if (isNaN(this.getUnsafe(i))) {
                    return true
                }
            }
            return false
        }
    {%- endif %}
        return this.indexOf(searchElement, asIntOrDefault(fromIndex, 0)) != -1
    }
    {%- if T != 'double' %}

    /**
     * Determines whether {{N}}Array includes a certain element, returning true or false as appropriate
     *
     * @param searchElement The element to search for
     *
     * @param fromIndex The position in this array at which to begin searching for searchElement
     *
     * @returns true if e is in {{N}}Array, false otherwise
     */
    public includes(searchElement: {{T}}, fromIndex: int): boolean {
        return this.indexOf(searchElement as int, fromIndex) != -1
    }

    /**
     * Determines whether {{N}}Array includes a certain element, returning true or false as appropriate
     *
     * @param searchElement The element to search for
     *
     * @param fromIndex The position in this array at which to begin searching for searchElement
     *
     * @returns true if searchElement is in {{N}}Array, false otherwise
     */
    public includes(searchElement: {{T}}): boolean {
        return this.includes(searchElement, 0)
    }
    {%- endif %}

    /**
     * Returns the index of the first occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the
     *  search starts at index 0.
     *
     * @returns index of element if it presents, -1 otherwise
     */
    public indexOf(searchElement: {{elementCompat}}, fromIndex?: number): number {
        return this.indexOf(searchElement, asIntOrDefault(fromIndex, 0))
    }

    /**
     * Returns the index of the first occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the
     *  search starts at index 0.
     *
     * @returns index of element if it presents, -1 otherwise
     */
    public indexOf(searchElement: {{elementCompat}}, fromIndex: int): number {
        {%- if elementCompat == 'BigInt' %}
            return this.indexOfImpl(searchElement.getLong(), fromIndex)
        {%- endif %}
        {%- if elementCompat == 'number' %}
            return this.indexOfImpl(searchElement, fromIndex)
        {%- endif %}
        {%- if elementCompat == 'int' %}
            return this.indexOfImpl(searchElement as long, fromIndex)
        {%- endif %}
    }

    /**
     * Returns the index of the first occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the
     *  search starts at index 0.
     *
     * @returns index of element if it presents, -1 otherwise
     */
    public indexOf(searchElement: int, fromIndex: int): number {
        return this.indexOfImpl(searchElement as long, fromIndex)
    }

    /**
     * Returns the index of the first occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the
     *  search starts at index 0.
     *
     * @returns index of element if it presents, -1 otherwise
     */
    private final native indexOfImpl(searchElement: number, fromIndex: int): number

    /**
     * Returns the index of the first occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the
     *  search starts at index 0.
     *
     * @returns index of element if it presents, -1 otherwise
     */
    private final native indexOfImpl(searchElement: long, fromIndex: int): number

    /**
     * Returns the index of the first occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is omitted, the
     *  search starts at index 0.
     *
     * @returns index of element if it presents, -1 otherwise
     */
    public indexOf(searchElement: int): number {
        return this.indexOf(searchElement, 0)
    }

    /**
     * Converts all elements of an array to strings and joins them using the specified separator.
     *
     * @param separator - separates one element of an array from the next in the resulting String.
     *
     * @returns joined representation
     */
    private final native joinInternal(separator: String): string;

    /**
     * Adds all the elements of an array separated by the specified separator string
     *
     * @param separator A string used to separate one element of an array from the next in the
     * resulting String. If omitted, the array elements are separated with a comma.
     *
     * @returns joined representation
     */
    public join(separator?: String): string {
        if (separator == undefined) {
            return this.joinInternal(",")
        }
        return this.joinInternal(separator)
    }

    /**
     * Returns an list of keys in {{N}}Array
     *
     * @returns iterator over keys
     */
    public keys(): IterableIterator<number> {
        return new {{N}}ArrayIteratorKeys(this)
    }

    /**
     * Returns the index of the last occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is undefined, the
     * search starts at index 0. If fromIndex is ommited, the search begins at index length-1
     *
     * @returns right-most index of searchElement. It must be less or equal than fromIndex. -1 if not found
     */
    public lastIndexOf(searchElement: {{elementCompat}}, fromIndex: number|undefined): number {
        return this.lastIndexOf(searchElement, asIntOrDefault(fromIndex, 0))
    }

    /**
     * Returns the index of the last occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is undefined, the
     * search starts at index 0. If fromIndex is ommited, the search begins at index length-1
     *
     * @returns right-most index of searchElement. It must be less or equal than fromIndex. -1 if not found
     */
    public lastIndexOf(searchElement: {{elementCompat}}): number {
        return this.lastIndexOf(searchElement, this.lengthInt - 1)
    }

    /**
     * Returns the index of the last occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is undefined, the
     * search starts at index 0. If fromIndex is ommited, the search begins at index length-1
     *
     * @returns right-most index of searchElement. It must be less or equal than fromIndex. -1 if not found
     */
    public lastIndexOf(searchElement: {{elementCompat}}, fromIndex: int): number {
        {%- if elementCompat == 'BigInt' %}
            return this.lastIndexOfImpl(searchElement.getLong(), fromIndex)
        {%- endif %}
        {%- if elementCompat == 'number' %}
            return this.lastIndexOfImpl(searchElement, fromIndex)
        {%- endif %}
        {%- if elementCompat == 'int' %}
            return this.lastIndexOfImpl(searchElement as long, fromIndex)
        {%- endif %}
    }

    /**
     * Returns the index of the last occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is undefined, the
     * search starts at index 0. If fromIndex is ommited, the search begins at index length-1
     *
     * @returns right-most index of searchElement. It must be less or equal than fromIndex. -1 if not found
     */
    public lastIndexOf(searchElement: int, fromIndex: int): number {
        return this.lastIndexOfImpl(searchElement as long, fromIndex)
    }

    /**
     * Returns the index of the last occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is undefined, the
     * search starts at index 0. If fromIndex is ommited, the search begins at index length-1
     *
     * @returns right-most index of searchElement. It must be less or equal than fromIndex. -1 if not found
     */
    private final native lastIndexOfImpl(searchElement: number, fromIndex: int): number

    /**
     * Returns the index of the last occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is undefined, the
     * search starts at index 0. If fromIndex is ommited, the search begins at index length-1
     *
     * @returns right-most index of searchElement. It must be less or equal than fromIndex. -1 if not found
     */
    private final native lastIndexOfImpl(searchElement: long, fromIndex: int): number

    /**
     * Returns the index of the last occurrence of a value in {{N}}Array.
     *
     * @param searchElement The value to locate in the array.
     *
     * @param fromIndex The array index at which to begin the search. If fromIndex is undefined, the
     * search starts at index 0. If fromIndex is ommited, the search begins at index length-1
     *
     * @returns right-most index of searchElement. It must be less or equal than fromIndex. -1 if not found
     */
    public lastIndexOf(searchElement: int): number {
        return this.lastIndexOf(searchElement, this.lengthInt - 1)
    }

    /**
    * Creates a new {{N}}Array using initializer
    *
    * @param data initializer
    *
    * @returns a new {{N}}Array from data
    */
    public of(data: FixedArray<Object>): {{N}}Array {
        throw new Error("{{N}}Array.of: not implemented")
    }

    /**
     * Creates a new {{N}}Array using reversed data from the current one
     *
     * @returns a new {{N}}Array using reversed data from the current one
     */
    public native reverse(): {{N}}Array

    /**
     * Creates a slice of current {{N}}Array using range [begin, end)
     *
     * @param begin start index to be taken into slice
     *
     * @param end last index to be taken into slice
     *
     * @returns a new {{N}}Array with elements of current {{N}}Array[begin;end) where end index is excluded
     *
     * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice
     */
    public slice(begin?: number, end?: number): {{N}}Array {
        return this.slice(asIntOrDefault(begin, 0 as int), asIntOrDefault(end, this.lengthInt))
    }

    /**
     * Creates a slice of current {{N}}Array using range [begin, end)
     *
     * @param begin start index to be taken into slice
     *
     * @param end last index to be taken into slice
     *
     * @returns a new {{N}}Array with elements of current {{N}}Array[begin;end) where end index is excluded
     *
     * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice
     */
    public slice(begin: number, end: number): {{N}}Array {
        return this.slice(begin as int, end as int)
    }

    /**
     * Creates a slice of current {{N}}Array using range [begin, end)
     *
     * @param begin start index to be taken into slice
     *
     * @param end last index to be taken into slice
     *
     * @returns a new {{N}}Array with elements of current {{N}}Array[begin;end) where end index is excluded
     *
     * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice
     */
    public slice(begin: number, end: int): {{N}}Array {
        return this.slice(begin as int, end as int)
    }

    /**
     * Creates a slice of current {{N}}Array using range [begin, end)
     *
     * @param begin start index to be taken into slice
     *
     * @param end last index to be taken into slice
     *
     * @returns a new {{N}}Array with elements of current {{N}}Array[begin;end) where end index is excluded
     *
     * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice
     */
    public slice(begin: int, end: number): {{N}}Array {
        return this.slice(begin as int, end as int)
    }

    /**
     * Creates a slice of current {{N}}Array using range [begin, end)
     *
     * @param begin start index to be taken into slice
     *
     * @param end last index to be taken into slice
     *
     * @returns a new {{N}}Array with elements of current {{N}}Array[begin;end) where end index is excluded
     *
     * @link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/slice
     */
    public slice(begin: int, end: int): {{N}}Array {
        const len: int = this.lengthInt
        const relStart = normalizeIndex(begin, len)
        const relEnd = normalizeIndex(end, len)
        let count = relEnd - relStart
        if (count < 0) {
            count = 0
        }
        let buf = this.buffer.slice(relStart * {{N}}Array.BYTES_PER_ELEMENT as int, relEnd * {{N}}Array.BYTES_PER_ELEMENT as int) as ArrayBuffer
        return new {{N}}Array(buf)
    }

    /**
     * Creates a slice of current {{N}}Array using range [begin, this.length).
     *
     * @param begin start index to be taken into slice
     *
     * @returns a new {{N}}Array with elements of current {{N}}Array[begin, this.length)
     */
    public slice(begin: number): {{N}}Array {
        return this.slice(begin as int)
    }

    /**
     * Creates a slice of current {{N}}Array using range [begin, this.length).
     *
     * @param begin start index to be taken into slice
     *
     * @returns a new {{N}}Array with elements of current {{N}}Array[begin, this.length)
     */
    public slice(begin: int): {{N}}Array {
        return this.slice(begin, this.lengthInt)
    }

    /**
     * Creates a {{N}}Array with the same underlying ArrayBuffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new {{N}}Array with the same underlying ArrayBuffer
     */
    public subarray(begin?: number, end?: number): {{N}}Array {
        return this.subarray(asIntOrDefault(begin, 0 as int), asIntOrDefault(end, this.lengthInt))
    }

    /**
     * Creates a {{N}}Array with the same underlying ArrayBuffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new {{N}}Array with the same underlying ArrayBuffer
     */
    public subarray(begin: number, end: number): {{N}}Array {
        return this.subarray(begin as int, end as int)
    }

    /**
     * Creates a {{N}}Array with the same underlying ArrayBuffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new {{N}}Array with the same underlying ArrayBuffer
     */
    public subarray(begin: number, end: int): {{N}}Array {
        return this.subarray(begin as int, end as int)
    }

    /**
     * Creates a {{N}}Array with the same underlying ArrayBuffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new {{N}}Array with the same underlying ArrayBuffer
     */
    public subarray(begin: int, end: number): {{N}}Array {
        return this.subarray(begin as int, end as int)
    }

    /**
     * Creates a {{N}}Array with the same underlying ArrayBuffer
     *
     * @param begin start index, inclusive
     *
     * @param end last index, exclusive
     *
     * @returns new {{N}}Array with the same underlying ArrayBuffer
     */
    public subarray(begin: int, end: int | undefined = this.lengthInt): {{N}}Array {
        const len: int = this.lengthInt
        const relStart = normalizeIndex(begin, len)
        const relEnd = normalizeIndex(end ?? this.lengthInt, len)
        let count = relEnd - relStart
        if (count < 0) {
            count = 0
        }
        return new {{N}}Array(this.buffer, relStart * {{N}}Array.BYTES_PER_ELEMENT as int, count)
    }

    /**
     * Converts {{N}}Array to a string with respect to locale
     *
     * @param locales
     *
     * @param options
     *
     * @returns string representation
     */
    public toLocaleString(locales: Object, options: Object): string {
        throw new Error("{{N}}Array.toLocaleString: not implemented")
    }

    /**
     * Converts {{N}}Array to a string with respect to locale
     *
     * @param locales
     *
     * @returns string representation
     */
    public toLocaleString(locales: Object): string {
        return this.toLocaleString(new Object(), new Object())
    }

    /**
     * Converts {{N}}Array to a string with respect to locale
     *
     * @returns string representation
     */
    public toLocaleString(): string {
        let res: StringBuilder = new StringBuilder("")
        for (let i = 0; i < this.lengthInt - 1; ++i) {
        {%- if N != 'BigInt64' %}
            res.append((this.getUnsafe(i) as {{subsetTypeValues}}).toLocaleString())
        {%- else %}
            res.append(new BigInt(this.getUnsafe(i)).toLocaleString())
        {%- endif %}
            res.append(",")
        }
        if (this.lengthInt > 0) {
        {%- if N != 'BigInt64' %}
            res.append((this.getUnsafe(this.lengthInt - 1) as {{subsetTypeValues}}).toLocaleString())
        {%- else %}
            res.append(new BigInt(this.getUnsafe(this.lengthInt - 1)).toLocaleString())
        {%- endif %}
        }
        return res.toString()
    }

    /**
     * Creates a reversed copy
     *
     * @returns a reversed copy
     */
    public final native toReversed(): {{N}}Array

    /**
     * Creates a sorted copy
     *
     * @returns a sorted copy
     */
    public toSorted(): {{N}}Array {
        return new {{N}}Array(this).sort()
    }

    /**
     * Returns a string representation of the {{N}}Array
     *
     * @returns a string representation of the {{N}}Array
     */
    public override toString(): string {
        return this.join(",")
    }

    /**
     * Returns array values iterator
     *
     * @returns an iterator
     */
    public values(): IterableIterator<{{subsetTypeValues}}> {
        return new {{N}}ArrayIterator(this)
    }

    /**
     * Iteratorable interface implementation
     *
     * @returns iterator over all elements
     */
    public override $_iterator(): IterableIterator<{{subsetTypeValues}}> {
        return this.values()
    }

    /**
     * Creates a copy with replaced value on index
     *
     * @param index
     *
     * @param value
     *
     * @returns an {{N}}Array with replaced value on index
     */
    public with(index: number, value: {{elementCompat}}): {{N}}Array {
        return this.with(index as int, value{{fromElementCompat}})
    }

    /**
     * Creates a copy with replaced value on index
     *
     * @param index
     *
     * @param value
     *
     * @returns an {{N}}Array with replaced value on index
     */
    public with(index: int, value: {{T}}): {{N}}Array {
        let res = new {{N}}Array(this)
        res.set(index, value)
        return res
    }

    /// === with element lambda functions ===
    {#- NOTE: to add overloads with idxType == 'int' we need to enhance generics supports #}
    {%- for idxType, castToIdx in [('number', ' as number')] %}
        {%- set skip = False %}
        {%- set elType, castToEl, castFromEl = elementCompat, asElementCompat, fromElementCompat %}
        {%- set retType = 'number | undefined'  if elementCompat == 'number' else 'BigInt | undefined' %}
        {%- if not skip %}

    /**
     * Finds the last element in the {{N}}Array that satisfies the condition
     *
     * @param fn condition
     *
     * @returns the last element that satisfies fn
     */
    public findLast(fn: (val: {{elType}}) => boolean): {{elType}} {
        let newF: (val: {{elType}}, index: number, array: {{N}}Array) => boolean =
            (val: {{elType}}, index: number, array: {{N}}Array): boolean => { return fn(val) }
        return {{castToEl % 'this.findLast(newF)'}}
    }

    /**
     * Sorts in-place by numeric value in ascending order.
     *
     * @returns sorted {{N}}Array
     */
    public native sort(): this;

    /**
     * Sorts in-place
     *
     * @param compareFn comparator _  used to determine the order of the elements.
     * compareFn returns a negative value if first argument is less than second argument,
     * zero if they're equal and a positive value otherwise.
     *
     * @returns sorted {{N}}Array
     */
    {%- if N == 'BigInt64' %}
    {%- set sortCompareFnRetTy = 'number | BigInt' %}
    {%- else %}
    {%- set sortCompareFnRetTy = 'number' %}
    {%- endif %}
    public sort(compareFn?: (a: {{elType}}, b: {{elType}}) => {{sortCompareFnRetTy}}): this {
        if (compareFn == undefined) {
            this.sort()
            return this
        }
        let arr: FixedArray<{{T}}> = new {{T}}[this.lengthInt]
        for (let i = 0; i < this.lengthInt; ++i) {
            arr[i] = this.getUnsafe(i)
        }

        let cmp = (l: {{T}}, r: {{T}}): number => {
            const result = compareFn!({{asElementCompat % 'l'}}, {{asElementCompat % 'r'}})
            {%- if N == 'BigInt64' %}
            if (result instanceof BigInt) {
                return (result as BigInt).getLong()
            } else {
                return result as number
            }
            {%- else %}
            return result
            {%- endif %}
        }
        const MAX_SHORT_LENGTH = 24
        if (arr.length > MAX_SHORT_LENGTH) {
            arr = mergeSort(arr, cmp)
        } else {
            sort(arr, cmp)
        }
        for (let i = 0; i < arr.length; ++i) {
            this.setUnsafe(i, arr[i])
        }
        return this
    }

        {%- endif %}{#- comment: end-condition for if not skip  #}

    /**
     * Determines whether the specified callback function returns true for any element of an array.
     *
     * @param predicate A function that accepts three arguments.
     * The some method calls the predicate function for each element in the array
     * until the predicate returns a true or until the end of the array.
     *
     * @returns false unless predicate function returns true for an array element,
     * in which case true is immediately returned.
     */
    public some(predicate: (element: {{elType}}, index: {{idxType}}, array: {{N}}Array) => boolean): boolean {
        for (let i = 0; i < this.lengthInt; ++i) {
            if (predicate({{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)) {
                return true
            }
        }
        return false
    }

    /**
     * Calls the specified callback function for all the elements in an array.
     * The return value of the callback function is the accumulated result,
     * and is provided as an argument in the next call to the callback function.
     *
     * @param callbackfn A function that accepts four arguments.
     * The reduce method calls the callbackfn function one time for each element in the array.
     *
     * @param initialValue The parameter which value is used as the initial value to start the accumulation.
     * The first call to the callbackfn function provides this value as an argument.
     *
     * @returns The value that results from running the callback function to completion over the entire typed array.
     */
    public reduce<U = {{elType}}>(
                callbackfn: (previousValue: U, currentValue: {{elType}}, currentIndex: {{idxType}}, array: {{N}}Array) => U,
                initialValue: U): U {
        let accumulatedValue = initialValue
        for (let i = 0; i < this.lengthInt; ++i) {
            accumulatedValue = callbackfn(accumulatedValue, {{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)
        }
        return accumulatedValue
    }

    /**
     * Calls the specified callback function for all the elements in an array.
     * The return value of the callback function is the accumulated result,
     * and is provided as an argument in the next call to the callback function.
     *
     * @param callbackfn A function that accepts four arguments.
     * The reduce method calls the callbackfn function one time for each element in the array.
     * The first call to the callbackfn function provides array first element value as an argument
     *
     * @returns The value that results from running the callback function to completion over the entire typed array.
     * calling reduce method on an empty array without an initial value creates a TypeError
     */
    public reduce(callbackfn: (previousValue: {{elType}}, currentValue: {{elType}}, currentIndex: {{idxType}}, array: {{N}}Array) => {{elType}}): {{elType}} {
        if (this.lengthInt == 0) {
            throw new TypeError("Reduce of empty array with no initial value")
        }

        let accumulatedValue = {{castToEl % 'this.getUnsafe(0)'}}
        for (let i = 1; i < this.lengthInt; ++i) {
            accumulatedValue = callbackfn(accumulatedValue, {{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)
        }
        return accumulatedValue
    }

    /**
     * Calls the specified callback function for all the elements in an array, in descending order.
     * The return value of the callback function is the accumulated result,
     * and is provided as an argument in the next call to the callback function.
     *
     * @param callbackfn A function that accepts four arguments.
     * The reduceRight method calls the callbackfn function one time for each element in the array.
     *
     * @param initialValue The parameter which value is used as the initial value to start the accumulation.
     * The first call to the callbackfn function provides this value as an argument.
     *
     * @returns The value that results from running the callback function to completion over the entire typed array.
     */
    public reduceRight<U = {{elType}}>(
                callbackfn: (previousValue: U, currentValue: {{elType}}, currentIndex: {{idxType}}, array: {{N}}Array) => U,
                initialValue: U): U {
        let accumulatedValue = initialValue
        for (let i = this.lengthInt - 1; i >= 0; --i) {
            accumulatedValue = callbackfn(accumulatedValue, {{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)
        }
        return accumulatedValue
    }


    /**
     * Calls the specified callback function for all the elements in an array, in descending order.
     * The return value of the callback function is the accumulated result,
     * and is provided as an argument in the next call to the callback function.
     *
     * @param callbackfn A function that accepts four arguments.
     * The reduceRight method calls the callbackfn function one time for each element in the array.
     * The first call to the callbackfn function provides array last element value as an argument
     *
     * @returns The value that results from running the callback function to completion over the entire typed array.
     * calling reduceRight method on an empty array without an initial value creates a TypeError
     */
    public reduceRight(callbackfn: (previousValue: {{elType}}, currentValue: {{elType}}, currentIndex: {{idxType}}, array: {{N}}Array) => {{elType}}): {{elType}} {
        if (this.lengthInt == 0) {
            throw new TypeError("Reduce of empty array with no initial value")
        }

        let accumulatedValue: {{elType}} = {{castToEl % 'this.getUnsafe(this.lengthInt - 1)'}}
        for (let i = this.lengthInt - 2; i >= 0; --i) {
            accumulatedValue = callbackfn(accumulatedValue, {{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)
        }
        return accumulatedValue
    }

   /**
    * Creates a new {{N}}Array using fn(arr[i]) over all elements of current {{N}}Array.
    *
    * @param fn a function to apply for each element of current {{N}}Array
    *
    * @returns a new {{N}}Array where for each element from current {{N}}Array fn was applied
    */
    public map(fn: (val: {{elType}}, index: {{idxType}}, array: {{N}}Array) => {{elType}}): {{N}}Array {
        let resBuf = new ArrayBuffer(this.lengthInt * {{N}}Array.BYTES_PER_ELEMENT as int)
        let res = new {{N}}Array(resBuf, 0, resBuf.getByteLength() / {{N}}Array.BYTES_PER_ELEMENT as int)
        for (let i = 0; i < this.lengthInt; ++i) {
            res.set(i, fn({{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this){{castFromEl}})
        }
        return res
    }

    /**
     * Determines whether the specified callback function returns true for all elements of an array.
     *
     * @param predicate A function that accepts three arguments.
     * The every method calls the predicate function for each element in the array until the predicate returns a false,
     * or until the end of the array.
     *
     * @returns true unless predicate function returns a false for an array element,
     * in which case false is immediately returned.
     */
    public every(predicate: (element: {{elType}}, index: {{idxType}}, array: {{N}}Array) => boolean): boolean {
        for (let i = 0; i < this.lengthInt; ++i) {
            if (!predicate({{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)) {
                return false
            }
        }
        return true
    }

    /**
     * Creates a new {{N}}Array from current {{N}}Array based on a condition fn.
     *
     * @param fn the condition to apply for each element
     *
     * @returns a new {{N}}Array with elements from current {{N}}Array that satisfy condition fn
     */
    public filter(fn: (val: {{elType}}, index: {{idxType}}, array: {{N}}Array) => boolean): {{N}}Array {
        let markers : FixedArray<boolean> = new boolean[this.lengthInt]
        let resLen = 0
        for (let i = 0; i < this.lengthInt; ++i) {
            markers[i] = fn({{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)
            if (markers[i]) {
                ++resLen
            }
        }
        let resBuf = new ArrayBuffer(resLen * {{N}}Array.BYTES_PER_ELEMENT as int)
        let res = new {{N}}Array(resBuf, 0)
        for (let i = 0, j = 0; i < this.lengthInt; ++i) {
            if (markers[i]) {
                res.set(j, this.getUnsafe(i))
                ++j
            }
        }
        return res
    }

    /**
     * Returns the value of the first element in the array where predicate is true, and undefined
     * otherwise
     *
     * @param predicate find calls predicate once for each element of the array, in ascending
     * order, until it finds one where predicate returns true. If such an element is found, find
     * immediately returns that element value. Otherwise, find returns undefined
     *
     * @returns {{retType}}
     */
    public find(predicate: (value: {{elType}}, index: {{idxType}}, obj: {{N}}Array) => boolean): {{retType}} {
        for (let i = 0; i < this.lengthInt; ++i) {
            let val = this.getUnsafe(i)
            if (predicate({{asElementCompat % 'val'}}, i{{castToIdx}}, this)) {
                return {{asElementCompat % 'val'}}
            }
        }
        return undefined
    }

    /**
     * Returns the index of the first element in the array where predicate is true, and -1
     * otherwise
     *
     * @param predicate find calls predicate once for each element of the array, in ascending
     * order, until it finds one where predicate returns true. If such an element is found,
     * findIndex immediately returns that element index. Otherwise, findIndex returns -1
     *
     * @returns {{idxType}}
     */
    public findIndex(predicate: (value: {{elType}}, index: {{idxType}}, obj: {{N}}Array) => boolean): {{idxType}} {
        for (let i = 0; i < this.lengthInt; ++i) {
            if (predicate({{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)) {
                return i{{castToIdx}}
            }
        }
        return -1{{castToIdx}}
    }

    /**
     * Finds the last element in the {{N}}Array that satisfies the condition
     *
     * @param fn condition
     *
     * @returns the last element that satisfies fn
     */
    public findLast(fn: (val: {{elType}}, index: {{idxType}}, array: {{N}}Array) => boolean): {{T}} {
        for (let i = this.lengthInt - 1; i >= 0; --i) {
            let val = this.getUnsafe(i)
            if (fn({{castToEl % 'val'}}, i{{castToIdx}}, this)) {
                return val
            }
        }
        throw new Error("{{N}}Array.findLast: not implemented if an element was not found")
    }

    /**
     * Finds an index of the last element in the {{N}}Array that satisfies the condition
     *
     * @param fn condition
     *
     * @returns the index of the last element that satisfies fn, -1 otherwise
     */
    public findLastIndex(fn: (val: {{elType}}, index: {{idxType}}, array: {{N}}Array) => boolean): {{idxType}} {
        for (let i = this.lengthInt - 1; i >= 0; --i) {
            let val = this.getUnsafe(i)
            if (fn({{castToEl % 'val'}}, i{{castToIdx}}, this)) {
                return i
            }
        }
        return -1{{castToIdx}}
    }

    /**
     * Performs the specified action for each element in {{N}}Array
     *
     * @param callbackfn  A function that accepts up to three arguments. forEach calls the
     * callbackfn function one time for each element in the array.
     *
     * @returns None
     */
    public forEach(callbackfn: (value: {{elType}}, index: number, array: {{N}}Array) => void): void {
        for (let i = 0; i < this.lengthInt; ++i) {
            callbackfn({{castToEl % 'this.getUnsafe(i)'}}, i{{castToIdx}}, this)
        }
    }

    {%- endfor %}

    /**
     * Returns the object itself
     *
     * @returns {{N}}Array
     */
    public valueOf(): {{N}}Array {
        return this
    }

    private final native getUnsafe(index: int): {{T}}

    internal setUnsafe(insertPos: int, val: {{T}}): void {
        let startByte = insertPos * {{N}}Array.BYTES_PER_ELEMENT as int + this.byteOffset as int
        {%- if T == "float" %}
        let bits = Float.bitCastToInt(val)
        {%- elif T == "double" %}
        let bits = Double.bitCastToLong(val)
        {%- else %}
        let bits = val
        {%- endif %}
        if (IS_LITTLE_ENDIAN) {
            for (let i = 0; i < {{N}}Array.BYTES_PER_ELEMENT as int; ++i) {
                let byteVal = ((bits >>> (i * 8)) & 0xff) as byte
                this.buffer.set(startByte + i, byteVal)
            }
        } else {
            for (let i = 0; i < {{N}}Array.BYTES_PER_ELEMENT as int; i++) {
                let byteVal = ((bits >>> (i * 8)) & 0xff) as byte
                this.buffer.set(startByte + {{S - 1}} - i, byteVal)
            }
        }
    }

    /** Underlying ArrayBuffer */
    public readonly buffer: ArrayBuffer

    /** Byte offset within the underlying ArrayBuffer */
    public readonly byteOffset: number

    /** Number of bytes used */
    public readonly byteLength: number

    /** String \"{{N}}Array\" */
    public readonly name = "{{N}}Array"
}
{%- endfor %}
